﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.System;
using Windows.UI.Core;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;

namespace Coding4Fun.Toolkit.Test.WindowsStore.Common
{
	/// <summary>
	/// Typical implementation of Page that provides several important conveniences:
	/// <list type="bullet">
	/// <item>
	/// <description>Application view state to visual state mapping</description>
	/// </item>
	/// <item>
	/// <description>GoBack, GoForward, and GoHome event handlers</description>
	/// </item>
	/// <item>
	/// <description>Mouse and keyboard shortcuts for navigation</description>
	/// </item>
	/// <item>
	/// <description>State management for navigation and process lifetime management</description>
	/// </item>
	/// <item>
	/// <description>A default view model</description>
	/// </item>
	/// </list>
	/// </summary>
	[Windows.Foundation.Metadata.WebHostHidden]
	public class LayoutAwarePage : Page
	{
		/// <summary>
		/// Identifies the <see cref="DefaultViewModel"/> dependency property.
		/// </summary>
		public static readonly DependencyProperty DefaultViewModelProperty =
			DependencyProperty.Register("DefaultViewModel", typeof(IObservableMap<String, Object>),
			typeof(LayoutAwarePage), null);

		private List<Control> _layoutAwareControls;

		/// <summary>
		/// Initializes a new instance of the <see cref="LayoutAwarePage"/> class.
		/// </summary>
		public LayoutAwarePage()
		{
			if (Windows.ApplicationModel.DesignMode.DesignModeEnabled) return;

			// Create an empty default view model
			this.DefaultViewModel = new ObservableDictionary<String, Object>();

			// When this page is part of the visual tree make two changes:
			// 1) Map application view state to visual state for the page
			// 2) Handle keyboard and mouse navigation requests
			this.Loaded += (sender, e) =>
			{
				this.StartLayoutUpdates(sender, e);

				// Keyboard and mouse navigation only apply when occupying the entire window
				if (this.ActualHeight == Window.Current.Bounds.Height &&
					this.ActualWidth == Window.Current.Bounds.Width)
				{
					// Listen to the window directly so focus isn't required
					Window.Current.CoreWindow.Dispatcher.AcceleratorKeyActivated +=
						CoreDispatcher_AcceleratorKeyActivated;
					Window.Current.CoreWindow.PointerPressed +=
						this.CoreWindow_PointerPressed;
				}
			};

			// Undo the same changes when the page is no longer visible
			this.Unloaded += (sender, e) =>
			{
				this.StopLayoutUpdates(sender, e);
				Window.Current.CoreWindow.Dispatcher.AcceleratorKeyActivated -=
					CoreDispatcher_AcceleratorKeyActivated;
				Window.Current.CoreWindow.PointerPressed -=
					this.CoreWindow_PointerPressed;
			};
		}

		/// <summary>
		/// An implementation of <see cref="IObservableMap&lt;String, Object&gt;"/> designed to be
		/// used as a trivial view model.
		/// </summary>
		protected IObservableMap<String, Object> DefaultViewModel
		{
			get
			{
				return this.GetValue(DefaultViewModelProperty) as IObservableMap<String, Object>;
			}

			set
			{
				this.SetValue(DefaultViewModelProperty, value);
			}
		}

		#region Navigation support

		/// <summary>
		/// Invoked as an event handler to navigate backward in the page's associated
		/// <see cref="Frame"/> until it reaches the top of the navigation stack.
		/// </summary>
		/// <param name="sender">Instance that triggered the event.</param>
		/// <param name="e">Event data describing the conditions that led to the event.</param>
		protected virtual void GoHome(object sender, RoutedEventArgs e)
		{
			// Use the navigation frame to return to the topmost page
			if (this.Frame != null)
			{
				while (this.Frame.CanGoBack) this.Frame.GoBack();
			}
		}

		/// <summary>
		/// Invoked as an event handler to navigate backward in the navigation stack
		/// associated with this page's <see cref="Frame"/>.
		/// </summary>
		/// <param name="sender">Instance that triggered the event.</param>
		/// <param name="e">Event data describing the conditions that led to the
		/// event.</param>
		protected virtual void GoBack(object sender, RoutedEventArgs e)
		{
			// Use the navigation frame to return to the previous page
			if (this.Frame != null && this.Frame.CanGoBack) this.Frame.GoBack();
		}

		/// <summary>
		/// Invoked as an event handler to navigate forward in the navigation stack
		/// associated with this page's <see cref="Frame"/>.
		/// </summary>
		/// <param name="sender">Instance that triggered the event.</param>
		/// <param name="e">Event data describing the conditions that led to the
		/// event.</param>
		protected virtual void GoForward(object sender, RoutedEventArgs e)
		{
			// Use the navigation frame to move to the next page
			if (this.Frame != null && this.Frame.CanGoForward) this.Frame.GoForward();
		}

		/// <summary>
		/// Invoked on every keystroke, including system keys such as Alt key combinations, when
		/// this page is active and occupies the entire window.  Used to detect keyboard navigation
		/// between pages even when the page itself doesn't have focus.
		/// </summary>
		/// <param name="sender">Instance that triggered the event.</param>
		/// <param name="args">Event data describing the conditions that led to the event.</param>
		private void CoreDispatcher_AcceleratorKeyActivated(CoreDispatcher sender,
			AcceleratorKeyEventArgs args)
		{
			var virtualKey = args.VirtualKey;

			// Only investigate further when Left, Right, or the dedicated Previous or Next keys
			// are pressed
			if ((args.EventType == CoreAcceleratorKeyEventType.SystemKeyDown ||
				args.EventType == CoreAcceleratorKeyEventType.KeyDown) &&
				(virtualKey == VirtualKey.Left || virtualKey == VirtualKey.Right ||
				(int)virtualKey == 166 || (int)virtualKey == 167))
			{
				var coreWindow = Window.Current.CoreWindow;
				var downState = CoreVirtualKeyStates.Down;
				bool menuKey = (coreWindow.GetKeyState(VirtualKey.Menu) & downState) == downState;
				bool controlKey = (coreWindow.GetKeyState(VirtualKey.Control) & downState) == downState;
				bool shiftKey = (coreWindow.GetKeyState(VirtualKey.Shift) & downState) == downState;
				bool noModifiers = !menuKey && !controlKey && !shiftKey;
				bool onlyAlt = menuKey && !controlKey && !shiftKey;

				if (((int)virtualKey == 166 && noModifiers) ||
					(virtualKey == VirtualKey.Left && onlyAlt))
				{
					// When the previous key or Alt+Left are pressed navigate back
					args.Handled = true;
					this.GoBack(this, new RoutedEventArgs());
				}
				else if (((int)virtualKey == 167 && noModifiers) ||
					(virtualKey == VirtualKey.Right && onlyAlt))
				{
					// When the next key or Alt+Right are pressed navigate forward
					args.Handled = true;
					this.GoForward(this, new RoutedEventArgs());
				}
			}
		}

		/// <summary>
		/// Invoked on every mouse click, touch screen tap, or equivalent interaction when this
		/// page is active and occupies the entire window.  Used to detect browser-style next and
		/// previous mouse button clicks to navigate between pages.
		/// </summary>
		/// <param name="sender">Instance that triggered the event.</param>
		/// <param name="args">Event data describing the conditions that led to the event.</param>
		private void CoreWindow_PointerPressed(CoreWindow sender,
			PointerEventArgs args)
		{
			var properties = args.CurrentPoint.Properties;

			// Ignore button chords with the left, right, and middle buttons
			if (properties.IsLeftButtonPressed || properties.IsRightButtonPressed ||
				properties.IsMiddleButtonPressed) return;

			// If back or foward are pressed (but not both) navigate appropriately
			bool backPressed = properties.IsXButton1Pressed;
			bool forwardPressed = properties.IsXButton2Pressed;
			if (backPressed ^ forwardPressed)
			{
				args.Handled = true;
				if (backPressed) this.GoBack(this, new RoutedEventArgs());
				if (forwardPressed) this.GoForward(this, new RoutedEventArgs());
			}
		}

		#endregion

		#region Visual state switching

		/// <summary>
		/// Invoked as an event handler, typically on the <see cref="FrameworkElement.Loaded"/>
		/// event of a <see cref="Control"/> within the page, to indicate that the sender should
		/// start receiving visual state management changes that correspond to application view
		/// state changes.
		/// </summary>
		/// <param name="sender">Instance of <see cref="Control"/> that supports visual state
		/// management corresponding to view states.</param>
		/// <param name="e">Event data that describes how the request was made.</param>
		/// <remarks>The current view state will immediately be used to set the corresponding
		/// visual state when layout updates are requested.  A corresponding
		/// <see cref="FrameworkElement.Unloaded"/> event handler connected to
		/// <see cref="StopLayoutUpdates"/> is strongly encouraged.  Instances of
		/// <see cref="LayoutAwarePage"/> automatically invoke these handlers in their Loaded and
		/// Unloaded events.</remarks>
		/// <seealso cref="DetermineVisualState"/>
		/// <seealso cref="InvalidateVisualState"/>
		public void StartLayoutUpdates(object sender, RoutedEventArgs e)
		{
			var control = sender as Control;
			if (control == null) return;
			if (this._layoutAwareControls == null)
			{
				// Start listening to view state changes when there are controls interested in updates
				Window.Current.SizeChanged += this.WindowSizeChanged;
				this._layoutAwareControls = new List<Control>();
			}
			this._layoutAwareControls.Add(control);

			// Set the initial visual state of the control
			VisualStateManager.GoToState(control, DetermineVisualState(ApplicationView.Value), false);
		}

		private void WindowSizeChanged(object sender, WindowSizeChangedEventArgs e)
		{
			this.InvalidateVisualState();
		}

		/// <summary>
		/// Invoked as an event handler, typically on the <see cref="FrameworkElement.Unloaded"/>
		/// event of a <see cref="Control"/>, to indicate that the sender should start receiving
		/// visual state management changes that correspond to application view state changes.
		/// </summary>
		/// <param name="sender">Instance of <see cref="Control"/> that supports visual state
		/// management corresponding to view states.</param>
		/// <param name="e">Event data that describes how the request was made.</param>
		/// <remarks>The current view state will immediately be used to set the corresponding
		/// visual state when layout updates are requested.</remarks>
		/// <seealso cref="StartLayoutUpdates"/>
		public void StopLayoutUpdates(object sender, RoutedEventArgs e)
		{
			var control = sender as Control;
			if (control == null || this._layoutAwareControls == null) return;
			this._layoutAwareControls.Remove(control);
			if (this._layoutAwareControls.Count == 0)
			{
				// Stop listening to view state changes when no controls are interested in updates
				this._layoutAwareControls = null;
				Window.Current.SizeChanged -= this.WindowSizeChanged;
			}
		}

		/// <summary>
		/// Translates <see cref="ApplicationViewState"/> values into strings for visual state
		/// management within the page.  The default implementation uses the names of enum values.
		/// Subclasses may override this method to control the mapping scheme used.
		/// </summary>
		/// <param name="viewState">View state for which a visual state is desired.</param>
		/// <returns>Visual state name used to drive the
		/// <see cref="VisualStateManager"/></returns>
		/// <seealso cref="InvalidateVisualState"/>
		protected virtual string DetermineVisualState(ApplicationViewState viewState)
		{
			return viewState.ToString();
		}

		/// <summary>
		/// Updates all controls that are listening for visual state changes with the correct
		/// visual state.
		/// </summary>
		/// <remarks>
		/// Typically used in conjunction with overriding <see cref="DetermineVisualState"/> to
		/// signal that a different value may be returned even though the view state has not
		/// changed.
		/// </remarks>
		public void InvalidateVisualState()
		{
			if (this._layoutAwareControls != null)
			{
				string visualState = DetermineVisualState(ApplicationView.Value);
				foreach (var layoutAwareControl in this._layoutAwareControls)
				{
					VisualStateManager.GoToState(layoutAwareControl, visualState, false);
				}
			}
		}

		#endregion

		#region Process lifetime management

		private String _pageKey;

		/// <summary>
		/// Invoked when this page is about to be displayed in a Frame.
		/// </summary>
		/// <param name="e">Event data that describes how this page was reached.  The Parameter
		/// property provides the group to be displayed.</param>
		protected override void OnNavigatedTo(NavigationEventArgs e)
		{
			// Returning to a cached page through navigation shouldn't trigger state loading
			if (this._pageKey != null) return;

			var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
			this._pageKey = "Page-" + this.Frame.BackStackDepth;

			if (e.NavigationMode == NavigationMode.New)
			{
				// Clear existing state for forward navigation when adding a new page to the
				// navigation stack
				var nextPageKey = this._pageKey;
				int nextPageIndex = this.Frame.BackStackDepth;
				while (frameState.Remove(nextPageKey))
				{
					nextPageIndex++;
					nextPageKey = "Page-" + nextPageIndex;
				}

				// Pass the navigation parameter to the new page
				this.LoadState(e.Parameter, null);
			}
			else
			{
				// Pass the navigation parameter and preserved page state to the page, using
				// the same strategy for loading suspended state and recreating pages discarded
				// from cache
				this.LoadState(e.Parameter, (Dictionary<String, Object>)frameState[this._pageKey]);
			}
		}

		/// <summary>
		/// Invoked when this page will no longer be displayed in a Frame.
		/// </summary>
		/// <param name="e">Event data that describes how this page was reached.  The Parameter
		/// property provides the group to be displayed.</param>
		protected override void OnNavigatedFrom(NavigationEventArgs e)
		{
			var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
			var pageState = new Dictionary<String, Object>();
			this.SaveState(pageState);
			frameState[_pageKey] = pageState;
		}

		/// <summary>
		/// Populates the page with content passed during navigation.  Any saved state is also
		/// provided when recreating a page from a prior session.
		/// </summary>
		/// <param name="navigationParameter">The parameter value passed to
		/// <see cref="Frame.Navigate(Type, Object)"/> when this page was initially requested.
		/// </param>
		/// <param name="pageState">A dictionary of state preserved by this page during an earlier
		/// session.  This will be null the first time a page is visited.</param>
		protected virtual void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
		{
		}

		/// <summary>
		/// Preserves state associated with this page in case the application is suspended or the
		/// page is discarded from the navigation cache.  Values must conform to the serialization
		/// requirements of <see cref="SuspensionManager.SessionState"/>.
		/// </summary>
		/// <param name="pageState">An empty dictionary to be populated with serializable state.</param>
		protected virtual void SaveState(Dictionary<String, Object> pageState)
		{
		}

		#endregion

		/// <summary>
		/// Implementation of IObservableMap that supports reentrancy for use as a default view
		/// model.
		/// </summary>
		private class ObservableDictionary<K, V> : IObservableMap<K, V>
		{
			private class ObservableDictionaryChangedEventArgs : IMapChangedEventArgs<K>
			{
				public ObservableDictionaryChangedEventArgs(CollectionChange change, K key)
				{
					this.CollectionChange = change;
					this.Key = key;
				}

				public CollectionChange CollectionChange { get; private set; }
				public K Key { get; private set; }
			}

			private Dictionary<K, V> _dictionary = new Dictionary<K, V>();
			public event MapChangedEventHandler<K, V> MapChanged;

			private void InvokeMapChanged(CollectionChange change, K key)
			{
				var eventHandler = MapChanged;
				if (eventHandler != null)
				{
					eventHandler(this, new ObservableDictionaryChangedEventArgs(change, key));
				}
			}

			public void Add(K key, V value)
			{
				this._dictionary.Add(key, value);
				this.InvokeMapChanged(CollectionChange.ItemInserted, key);
			}

			public void Add(KeyValuePair<K, V> item)
			{
				this.Add(item.Key, item.Value);
			}

			public bool Remove(K key)
			{
				if (this._dictionary.Remove(key))
				{
					this.InvokeMapChanged(CollectionChange.ItemRemoved, key);
					return true;
				}
				return false;
			}

			public bool Remove(KeyValuePair<K, V> item)
			{
				V currentValue;
				if (this._dictionary.TryGetValue(item.Key, out currentValue) &&
					Object.Equals(item.Value, currentValue) && this._dictionary.Remove(item.Key))
				{
					this.InvokeMapChanged(CollectionChange.ItemRemoved, item.Key);
					return true;
				}
				return false;
			}

			public V this[K key]
			{
				get
				{
					return this._dictionary[key];
				}
				set
				{
					this._dictionary[key] = value;
					this.InvokeMapChanged(CollectionChange.ItemChanged, key);
				}
			}

			public void Clear()
			{
				var priorKeys = this._dictionary.Keys.ToArray();
				this._dictionary.Clear();
				foreach (var key in priorKeys)
				{
					this.InvokeMapChanged(CollectionChange.ItemRemoved, key);
				}
			}

			public ICollection<K> Keys
			{
				get { return this._dictionary.Keys; }
			}

			public bool ContainsKey(K key)
			{
				return this._dictionary.ContainsKey(key);
			}

			public bool TryGetValue(K key, out V value)
			{
				return this._dictionary.TryGetValue(key, out value);
			}

			public ICollection<V> Values
			{
				get { return this._dictionary.Values; }
			}

			public bool Contains(KeyValuePair<K, V> item)
			{
				return this._dictionary.Contains(item);
			}

			public int Count
			{
				get { return this._dictionary.Count; }
			}

			public bool IsReadOnly
			{
				get { return false; }
			}

			public IEnumerator<KeyValuePair<K, V>> GetEnumerator()
			{
				return this._dictionary.GetEnumerator();
			}

			System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
			{
				return this._dictionary.GetEnumerator();
			}

			public void CopyTo(KeyValuePair<K, V>[] array, int arrayIndex)
			{
				int arraySize = array.Length;
				foreach (var pair in this._dictionary)
				{
					if (arrayIndex >= arraySize) break;
					array[arrayIndex++] = pair;
				}
			}
		}
	}
}
